// ---------
// Constants
// ---------

var REFLECTOR = '/~caffeine/lhcal/reflect.py';
var FEED = '/~caffeine/lhcal/feed.py';
var THROBBER = '/~caffeine/lhcal/throbber.gif';

// -------
// Globals
// -------

var albums = null;
var debuglog = null;

// -------
// Helpers
// -------

/**
 * Looks up an id in the DOM.
 * @param {String} id the id to look for
 * @return {Object} the DOM element if found, null otherwise
 */
function el(id) {
  if (document.getElementById) {
    return document.getElementById(id);
  }

  if (document.all) {
    return document.all[id];
  }

  return null;
}

/**
 * Creates a new XMLHttpRequest object or MS equivalent.
 * @return {XMLHttpRequest} a new XMLHttpRequest object, or null on failure
 */
function xh() {
  if (window.XMLHttpRequest) {
    return new XMLHttpRequest();
  } else if (window.ActiveXObject) {
    try {
      return new ActiveXObject("Msxml2.XMLHTTP");
    } catch (ex) {
      try {
        return new ActiveXObject("Microsoft.XMLHTTP");
      } catch (ex) {
      }
    }
  }

  return null;
}

/**
 * Html-escapes a string.
 * @param {String} str the string to escape
 * @param {String} opt_default optional string returned if str is null or empty
 * @return {String} an html-escaped string
 */
function htmlesc(str, opt_default) {
  if (null == str || 0 == str.length) {
    if (opt_default && opt_default.length) {
      str = opt_default;
    } else {
      return '';
    }
  }

  var div = document.createElement('div');
  var text = document.createTextNode(str);
  div.appendChild(text);
  return div.innerHTML;
}

/**
 * Shows the debug area.
 * @param {String} id of the debug area div
 */
function showdebug(id) {
  var dc = el(id);
  if (!dc) {
    return;
  }

  if (dc.style.display == "none") {
    dc.style.display = "";
  } else {
    dc.style.display = "none";
  }
}

/**
 * Initializes the page.
 * @param {String} inputid DOM id of the username input
 * @param {String} outputid DOM id of the output div
 * @param {String} debugid DOM id of the debug div
 */
function init(inputid, outputid, debugid) {
  var userinput = el(inputid);
  if (userinput) {
    userinput.value = '';
    userinput.focus();
  }
  debuglog = new DebugLog(debugid);
  albums = new AlbumManager(inputid, outputid);

  // Preload the throbber.
  new Image().src = THROBBER;
}

// --------
// DebugLog
// --------

/**
 * Creates a new DebugLog.
 * @param {String} debugid DOM id of the debug div
 * @constructor
 */
function DebugLog(debugid) {
  this.debugdiv = el(debugid);

  if (null == this.debugdiv) {
    alert("Unable to find '" + debugid + "'");
  }
}

/**
 * Logs a debug message.
 * @param {String} message the message to log
 */
DebugLog.prototype.log = function(message) {
  var text = document.createTextNode(message);
  var br = document.createElement('br');
  this.debugdiv.appendChild(text);
  this.debugdiv.appendChild(br);
}

/**
 * Clears the debug log.
 */
DebugLog.prototype.clear = function() {
  this.debugdiv.children = [];
  this.debugdiv.innerHTML = '';
}

// ------------
// AlbumManager
// ------------

/**
 * Creates a new AlbumManager.
 * @param {String} inputid DOM id of the username input element
 * @param {String} outputid DOM id of the output div
 * @constructor
 */
function AlbumManager(inputid, outputid) {
  this.timeout = null;
  this.xmlhttp = null;
  this.throbbing = false;
  this.userinput = el(inputid);
  this.outputdiv = el(outputid);
  this.username = null;
  this.albums = {};
  this.nchecked = 0;

  if (null == this.userinput || null == this.outputdiv) {
    alert("Unable to find '" + inputid + "' or '" + outputid + "'");
  } else {
    this.showHelp();
  }
}

/**
 * Tells the album manager to maybe start fetching the album list.
 */
AlbumManager.prototype.getAlbums = function() {
  if (null == this.userinput ||
      null == this.userinput.value ||
      0 == this.userinput.value.length) {
    this.showHelp();
    return;
  }
  
  if (null != this.timeout) {
    clearTimeout(this.timeout);
  }

  var self = this;
  var username = this.userinput.value;

  // Do the actual fetch "soon", allowing the event queue to drain first.
  this.startThrobber();
  this.timeout =
    setTimeout(function() { self.getAlbumsInternal_(username); }, 0);
};

/**
 * Actually does a fetch of the album list.
 * @param {String} username of the user whose album list to fetch
 */
AlbumManager.prototype.getAlbumsInternal_ = function(username) {
  var url = [];
  url.push(REFLECTOR);
  url.push('/albumlist?user=');
  url.push(encodeURIComponent(username));

  // Cancel the current request, if any.
  if (null != this.xmlhttp) {
    this.xmlhttp.abort();
  }

  this.username = username;
  this.xmlhttp = xh();
  if (null == this.xmlhttp) {
    debuglog.log("Unable to create a new XMLHttpRequest");
    return;
  }

  var xmlhttp = this.xmlhttp;
  var self = this;
  xmlhttp.onreadystatechange = function() { self.onReadyStateChange_(); };
  xmlhttp.open('GET', url.join(''), true);
  xmlhttp.send(null);
};

/**
 * Manages the state change of our XmlHttp object.
 */
AlbumManager.prototype.onReadyStateChange_ = function() {
  var xmlhttp = this.xmlhttp;
  if (xmlhttp && 4 == xmlhttp.readyState) {
    this.xmlhttp = null;
    this.onAlbumsFetched_(xmlhttp);
  }
};

/**
 * Handles the response from our XmlHttp object.
 * @param {XMLHttpRequest} xmlhttp the xmlhttp request that has finished.
 */
AlbumManager.prototype.onAlbumsFetched_ = function(xmlhttp) {
  if (200 != xmlhttp.status) {
    this.showError();
    return;
  }

  var response = null;
  try {
    response = eval('(' + xmlhttp.responseText + ')');
  } catch (ex) {
    debuglog.log('Caught exception: ' + ex);
  }

  if (!response) {
    this.showError();
  }

  this.drawAlbums(response);
};

/**
 * Shows an error message in the output div.
 */
AlbumManager.prototype.showError = function() {
  var out = [];
  out.push('<div width=100% height=100% valign=middle align=center>');
  out.push('<b>');
  out.push('<font color=red>');
  out.push('Oops! We were unable to fetch your album feed.&nbsp;');
  out.push('Did you enter your username correctly?');
  out.push('</font>');
  out.push('</b>');
  out.push('</div>');

  this.dumpOutput_(out.join(''));
}

/**
 * Shows a help message in the output div.
 */
AlbumManager.prototype.showHelp = function() {
  var out = [];
  out.push('<center>');
  out.push('<i>');
  out.push('Enter your username on the left to get started.');
  out.push('</i>');
  out.push('</center>');

  this.dumpOutput_(out.join(''));
}

/**
 * Draws the album list in the output div.
 * @param {Object} albums the object represented in the album JSON feed
 */
AlbumManager.prototype.drawAlbums = function(feed) {
  var out = [];
  var err = null;

  if (!(feed && feed.feed)) {
    this.showError();
    return;
  }

  feed = feed.feed;
  var entries = feed.entry || [];
  var nalbums = 0;


  if (feed.icon && feed.author && feed.author.length > 0 &&
      feed.author[0].name && feed.author[0].uri) {
    out.push('<table width=100% border=0 cellpadding=2 cellspacing=0>');
    out.push('<tr><td>');
    out.push('<a href="' + feed.author[0].uri.$t + '">');
    out.push(
      '<img align=left class=icon src="' + feed.icon.$t +
      '" alt="' + htmlesc(feed.author[0].name) + '"/>');
    out.push('</a>');
    out.push('<b>')
    out.push('<a href="' + feed.author[0].uri.$t + '">');
    out.push(htmlesc(feed.author[0].name.$t));
    out.push("</a>'s photos");
    out.push('</b>');

    if (feed.openSearch$totalResults) {
      out.push('<br/>');
      out.push('<span class=slim>');
      out.push(entries.length + ' albums');
      out.push('</span>');
    }

    out.push('</td>');
    out.push('<td align=center>');
    out.push('<button id=cb onclick="albums.addToCalendar();">');
    out.push('<font size=+2>I\'m ready! Add my photos to Google Calendar!</font>');
    out.push('</button>');
    out.push('</td></tr>');
    out.push('</table>');
    out.push('<br/>');
  } else {
    debuglog.log('Unable to find author information in feed');
    err = 'No album information contained in feed';
  }

  this.albums = {};

  for (var i = 0; i < entries.length; i += 2) {
    out.push('<table width=100% border=0 cellpadding=0 cellspacing=0>');
    out.push('<tr>');
    for (var j = i; j < i + 2; ++j) {
      if (j == entries.length) {
        out.push('<td width=50%>&nbsp;</td>');
        continue;
      }

      var entry = entries[j];
      var links = entry.link || [];
      var title = htmlesc(entry.title.$t, '(no title)');
      var summary = htmlesc(entry.summary.$t, '(no description)');
      var nphotos = htmlesc(entry.gphoto$numphotos.$t, '0');
      var id = htmlesc(entry.gphoto$id.$t);
      var img = entry.media$group.media$thumbnail[0].url;
      var url = 'javascript:void(0)';
      ++nalbums;

      this.albums[id] = true;

      for (var k = 0; k < entry.link.length; ++k) {
        var link = entry.link[k];
        if ('alternate' == link.rel && 'text/html' == link.type) {
          url = link.href;
        }
      }

      out.push('<td id="td' + id + '" class="album included" width=50%>');
      out.push('<a href="' + url + '" target="_blank">');
      out.push(
          '<img align=left class=icon src="' + img + '" alt="' + title + '">');
      out.push('</a>');
      out.push('<b><a href="' + url + '" target="_blank">');
      out.push(title + '</a></b>');
      out.push('<br>');
      out.push('<span class=slim>');
      out.push(summary + '<br/>');
      out.push(nphotos + ' photos');
      out.push('</span><br/>');
      out.push(
          '<span class=check onclick="albums.toggleAlbum(\'' + id + '\');">');
      out.push('<input type=checkbox checked id="ck' + id + '"/>');
      out.push('<span id="sp' + id + '"/><b>Included</b></span>');
      out.push('</span>');
      out.push('</td>');
    }
    out.push('</tr>');
    out.push('</table>');
  }

  if (0 == nalbums) {
    err = 'This user has no albums';
  }

  if (null != err) {
    out.push('<center>')
    out.push('<i>' + htmlesc(err) + '</i>');
    out.push('</center>');
  }

  this.nchecked = nalbums;
  this.dumpOutput_(out.join(''));
  this.updateButton_();
}

/**
 * Toggles the checkbox for the album with the given id.
 * @param {String} id of the album to toggle
 */
AlbumManager.prototype.toggleAlbum = function(id) {
  var td = el('td' + id);
  var ck = el('ck' + id);
  var sp = el('sp' + id);

  if (null == td || null == ck || null == sp) {
    debuglog.log("Could not find elements for '" + id + "'");
    return;
  }

  if (this.albums[id]) {
    this.albums[id] = false;
    this.nchecked -= 1;
    ck.checked = false;
    td.className = 'album';
    sp.innerHTML = '<b>Excluded</b>';
  } else {
    this.albums[id] = true;
    this.nchecked += 1;
    ck.checked = true;
    td.className = 'album included';
    sp.innerHTML = '<b>Included</b>';
  }

  this.updateButton_();
}

/**
 * Updates the button active state.
 */
AlbumManager.prototype.updateButton_ = function() {
  debuglog.log('nchecked: ' + this.nchecked);

  var cb = el('cb');
  if (!cb) {
    debuglog.log("Could not get button element 'cb'");
    return;
  }

  if (this.nchecked <= 0) {
    cb.disabled = true;
    debuglog.log('Disabling button');
  } else {
    cb.disabled = false;
    debuglog.log('Enabling button');
  }
}

/**
 * Starts the throbber in the output div.
 */
AlbumManager.prototype.startThrobber = function() {
  if (this.throbbing) {
    return;
  }

  var out = [];

  out.push('<center>');
  out.push('<img src="' + THROBBER + '" alt="Loading..."/>');
  out.push('</center>');

  this.dumpOutput_(out.join(''), true);
}

/**
 * Generates the correct feed URL and sends the user to calendar to load it.
 */
AlbumManager.prototype.addToCalendar = function() {
  var tzoffset = (new Date()).getTimezoneOffset();
  var excludes = [];

  for (var id in this.albums) {
    if (false == this.albums[id]) {
      excludes.push(id);
    }
  }

  var feedurl = [];
  feedurl.push('http://');
  feedurl.push(location.host);
  feedurl.push(FEED)
  feedurl.push('/ical?user=');
  feedurl.push(encodeURIComponent(this.username));
  feedurl.push('&excludes=');
  feedurl.push(encodeURIComponent(excludes.join(',')));
  feedurl.push('&tzoffset=');
  feedurl.push(tzoffset);

  var url = [];
  url.push('http://www.google.com/calendar/render?cid=');
  url.push(encodeURIComponent(feedurl.join('')));

  debuglog.log("Feed url: '" + feedurl.join('') + "'");
  debuglog.log("Calendar url: '" + url.join('') + "'");
  window.open(url.join(''));
}

/**
 * Dumps the give output to the output div.
 * @param {String} output to dump
 * @param {Boolean} opt_throbbing whether the given output is a throbber
 */
AlbumManager.prototype.dumpOutput_ = function(output, opt_throbbing) {
  this.outputdiv.innerHTML = output;
  if (opt_throbbing) {
    this.throbbing = true;
  } else {
    this.throbbing = false;
  }
}
